﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Moki.Examples.Model
{
    public interface IContactBook
    {
        Contact GetContact(string Name);
    }

    public interface IMailService
    {
        IContactBook ContactBook { get; set; }
        void Send(string address, string message);
    }

    public class Contact
    {
        public string Name { get; set; }
        public string Email { get; set; }
        public string Address { get; set; }
    }

    public class Movie
    {
        public string Title { get; set; }
        public int Minutes { get; set; }
    }

    public class Show
    {
        public string Id;
        public int RoomNumber;
        public Movie Movie;
        public DateTime DateAndHour;
    }

    public class Booking
    {
        public Show show;
        public string PersonName;
        public int PlaceNo;
        public DateTime bookedDate;
    }

    public class CinemaBookingService
    {
        public IBookingDB database { get; set; }
        public IMailService mailService { get; set; }

        public CinemaBookingService(IBookingDB database)
        {
            this.database = database;
        }

        public CinemaBookingService(IBookingDB database, IMailService mailService)
        {
            this.database = database;
            this.mailService = mailService;

        }

        /// <summary>
        /// Rezerwuje miejsce na podany seans filmowy
        /// </summary>
        /// <param name="show">Seans filmowy</param>
        /// <param name="placeNo">Numer rezerwacji</param>
        /// <param name="bookingPerson">Nazwa osoby rezerwującej</param>
        public void BookPlace(Show show, int placeNo, string bookingPerson)
        {
            if (database.IsBooked(show, placeNo))
                throw new BookingException("Podane miejsce zostało już zarezerowane.");
            else
            {
                database.Book(show, placeNo, bookingPerson);
                sendMail(bookingPerson, "Zarezerwowano miejsce na seans " + show.ToString());
            }
        }

        private void sendMail(string bookingPerson, string message)
        {
            if (mailService != null)
            {
                string personMail = mailService.ContactBook.GetContact(bookingPerson).Email;
                mailService.Send(personMail, message);
            }
        }

        /// <summary>
        /// Anuluje rezerwację miejsca na podany seans 
        /// </summary>
        /// <param name="show">Seans filmowy</param>
        /// <param name="placeNo">Numer rezerwacji</param>
        public void CancelBooking(Show show, int placeNo)
        {
            Booking booking = database.GetBooking(show, placeNo);

            if(booking == null)
                throw new BookingException("Rezerwacja nie istnieje");
            else if ((DateTime.Now - booking.bookedDate).Hours > 24)
                throw new BookingException("Nie można odwołać rezerwacji po upływie 24 godz.");
            else
            {
                database.Cancel(show, placeNo);
                sendMail(booking.PersonName, "Anulowano miejsce z seansu " + show.ToString());
            }
        }
    }

    public interface IBookingDB
    {
        bool IsBooked(Show show, int placeNo);
        void Book(Show show, int placeNo, string bookingPerson);
        void Cancel(Show show, int placeNo);
        Booking GetBooking(Show show, int placeNo);
    }

    public class CachedBookingDB : IBookingDB
    {
        IBookingDB database;
        IDictionary<KeyValuePair<Show, int>, Booking> cache;

        public CachedBookingDB()
        {

        }

        public CachedBookingDB(IBookingDB database)
        {
            this.database = database;
            this.cache = new Dictionary<KeyValuePair<Show, int>, Booking>();
        }


        public bool IsBooked(Show seance, int placeNo)
        {
            throw new NotImplementedException();
        }

        public void Book(Show seance, int placeNo, string bookingPerson)
        {
            throw new NotImplementedException();
        }

        public void Cancel(Show saence, int placeNo)
        {
            throw new NotImplementedException();
        }

        public virtual Booking GetBooking(Show seance, int placeNo)
        {
            var key = new KeyValuePair<Show, int>(seance, placeNo);
            
            if(!cache.ContainsKey(key))
            {
                var booking = database.GetBooking(seance, placeNo);
                cache.Add(key, booking);
            }

            return cache[key];
        }
    }




    [Serializable]
    public class BookingException : Exception
    {
        public BookingException() { }
        public BookingException(string message) : base(message) { }
        public BookingException(string message, Exception inner) : base(message, inner) { }
        protected BookingException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }
    }
}
